//
//  parser.hpp
//  utilities
//
//  Created by Sam Jaffe on 2/4/15.
//  Copyright (c) 2015 Sam Jaffe. All rights reserved.
//

#pragma once

#include <functional>
#include <initializer_list>
#include <vector>

#include "parameter.hpp"
#include "config.hpp"

namespace options {
    bool is_dash_arg(const std::string& arg) {
        return arg[0] == '-';
    }
    
    template <typename opt, typename type>
    vparameter::parser make_vargs_parser(type& container, std::function<bool(const std::string&)> end_condition = is_dash_arg);
    template <typename opt, typename type>
    vparameter::parser make_vargs_parser(type& container, int min, int max, std::function<bool(const std::string&)> end_condition = is_dash_arg);
    
    template <typename opt, typename type>
    parameter::parser make_commalist_parser(type& container);
    
    template <class opt>
    class parser {
    private:
        using str_list = std::initializer_list<std::string>;
        using str_vec = std::vector<std::string>;
    public:
        template <class type>
        static void addopt(const std::string& name, type& val, bool required,
                           const std::string& desc = "");
        template <class type>
        static void addopt(str_list names, type& val, bool required,
                           const std::string& desc = "");
        static void addopt(std::string name, int arity, parameter::parser parser, bool required,
                           const std::string& desc = "", const str_vec & argument_types = {});
        static void addopt(str_list names, int arity, parameter::parser parser, bool required,
                           const std::string& desc = "", const str_vec & argument_types = {});
        static void addopt(std::string name, vparameter::parser parser, bool required,
                           const std::string& desc = "", const str_vec & argument_types = {"args..."});
        static void addopt(str_list names, vparameter::parser parser, bool required,
                           const std::string& desc = "", const str_vec & argument_types = {"args..."});
        
        template <class type>
        static void addopt(const std::string& name, type& val, const std::string& desc = "");
        template <class type>
        static void addopt(str_list names, type& val, const std::string& desc = "");
        static void addopt(std::string name, int arity, parameter::parser parser,
                           const std::string& desc = "", const str_vec & argument_types = {});
        static void addopt(str_list names, int arity, parameter::parser parser,
                           const std::string& desc = "", const str_vec & argument_types = {});
        static void addopt(std::string name, vparameter::parser parser,
                           const std::string& desc = "", const str_vec & argument_types = {"args..."});
        static void addopt(str_list names, vparameter::parser parser,
                           const std::string& desc = "", const str_vec & argument_types = {"args..."});
        
        template <class type>
        static void addopt(const std::string& name, type& val, const char* desc = "");
        template <class type>
        static void addopt(str_list names, type& val, const char* desc = "");
        static void addopt(std::string name, int arity, parameter::parser parser,
                           const char* desc = "", const str_vec & argument_types = {});
        static void addopt(str_list names, int arity, parameter::parser parser,
                           const char* desc = "", const str_vec & argument_types = {});
        static void addopt(std::string name, vparameter::parser parser,
                           const char* desc = "", const str_vec & argument_types = {"args..."});
        static void addopt(str_list names, vparameter::parser parser,
                           const char* desc = "", const str_vec & argument_types = {"args..."});

        
        static void parse_options(int argc, const char* argv[]);
        static int parse_options_until_verb(int argc, const char* argv[]);
        
        static const opt& value() { static opt v{}; return v; }
        static const std::string* end() { return config().end_of_args; }
        static const str_vec& args() { return config().main_args; }
    private:
        static bool parse_one_option(std::string* &arglist);
        static void init_usage_options(std::string mainprog) {
            config().mainprog = mainprog;
            config().usage_info.emplace("--help", description("--help", {}, "Print this message"));
        }
        
        static inline void add_each_option(str_list names, options::parameter_base* par, bool required, const str_vec & argument_types, const std::string& desc);
    private:
        static options::config& config() { static options::config c; return c; }
    };
}
